#include <linux/version.h>/* LINUX_VERSION_CODE KERNEL_VERSION()*/
#include <linux/init.h>   /* module_init module_exit */
#include <linux/kernel.h>
#include <linux/module.h> /* MODULE_LICENSE */

#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/io.h>

#include <linux/delay.h> /* ssleep */

#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>

#include <linux/gpio.h>

#include <linux/interrupt.h>

#include "./timestamp_autogenerated.h"
#include "./version_autogenerated.h"
#include "./sdrv_common.h"

// #undef PRT
// #undef PRTE
#ifndef PRT
#define PRT printk
#endif
#ifndef PRTE
#define PRTE printk
#endif

#define CHRDEV_NAME    ("sdev")    /* 设备名,cat /proc/devices 查看与设备号的对应关系 */
#define CLASS_NAME     ("sclass")  /* 类名，在 /sys/class 中显示的名称 */
#define DEVICE_NAME    ("sdevchr") /* 设备节点名，在 /sys/class/class_name/ 中显示的名称以及 /dev/ 下显示的节点名 */
#define BUFSIZE        (32)        /* 设置最大偏移量为32 */
#define GPIO_PIN       (18)        /* GPIOn_IOm ==> pin = (n-1)*32+m ==> GPIO1_IO18 = 0*32+18=18 */        
/* 中断IO描述结构体 */
typedef struct __irq_gpio_desc {
    char name[16];							/* 名字 */
	int gpio;								/* gpio */
	int irq_num;							/* 中断号     */
	unsigned char value;					/* 按键对应的键值 */
    unsigned long flags;                    /* 触发方式 */
	irqreturn_t (*handler)(int, void *);	/* 中断服务函数 */
}irq_gpio_desc_t;

typedef struct __CHAR_DEVICE
{
    char dev_name[32];     // 设备名称， /dev/dev-name
    dev_t dev_num;         // 定义dev_t类型（32位大小）的变量dev_num,用来存放设备号
    struct cdev s_cdev;    // 定义cdev结构体类型的变量scdev
    struct class *class;   // 定于struct class *类型结构体变量 class，表示要创建的类
    struct device *device; // 设备
    char buf[BUFSIZE];     // 设置数据存储数组mem

    struct device_node *nd; // 设备节点
    irq_gpio_desc_t irq_key;//按键的中断信息
    atomic_t key_value;		/* 按键值 */	
} _CHAR_DEVICE;

_CHAR_DEVICE g_chrdev = {0};  //定义一个device_test结构体变量

/**
 * @brief  imx6ull_key_read()
 * @note   读取key按键的值
 * @param [in] 
 * @param [out]
 * @retval 
 */
static int imx6ull_key_read(_CHAR_DEVICE *p_chrdev)
{
    int value = 0xff;

    if (gpio_get_value(p_chrdev->irq_key.gpio) == 0)
    { 
        /* key0按下 */
        while (!gpio_get_value(p_chrdev->irq_key.gpio)); /* 等待按键释放 */
        atomic_set(&p_chrdev->key_value, 0); // 按键值设置为0
    }
    else
    {
        atomic_set(&p_chrdev->key_value, 0xff); /* 无效的按键值 */
    }

    value = atomic_read(&p_chrdev->key_value);

    return value;
}

/**
 * @brief  gpio_irq_handler()
 * @note   中断处理函数
 * @param [in]
 * @param [out]
 * @retval 
 */
static irqreturn_t gpio_irq_handler(int irq, void *dev_id)
{
    _CHAR_DEVICE *p_chrdev=(_CHAR_DEVICE *)dev_id;

    PRT("Interrupt occurred on [p_chrdev->irq_key GPIO %d irq %d], irq=%d\n", 
            p_chrdev->irq_key.gpio, p_chrdev->irq_key.irq_num, irq);
    return IRQ_HANDLED;
}

/**
 * @brief  gpio_irq_init()
 * @note   gpio中断初始化
 * @param [in]
 * @param [out]
 * @retval 
 */
static int gpio_irq_init(_CHAR_DEVICE *p_chrdev)
{
    int ret = 0;
    if(p_chrdev == NULL)
    {
        return -1;
    }

    memset(p_chrdev->irq_key.name, 0, sizeof(p_chrdev->irq_key.name));	/* 缓冲区清零 */
	sprintf(p_chrdev->irq_key.name, "irq_key_%d", 0);		/* 组合名字 */
    
    p_chrdev->irq_key.gpio = GPIO_PIN;
    p_chrdev->irq_key.handler = gpio_irq_handler;
    p_chrdev->irq_key.flags = IRQF_TRIGGER_RISING;
    
    p_chrdev->irq_key.irq_num = gpio_to_irq(p_chrdev->irq_key.gpio); // 将GPIO引脚映射到中断号
    PRT("GPIO %d mapped to IRQ %d\n", GPIO_PIN, p_chrdev->irq_key.irq_num);

    // 请求中断
    ret = request_irq(p_chrdev->irq_key.irq_num, p_chrdev->irq_key.handler, 
                      p_chrdev->irq_key.flags, p_chrdev->irq_key.name, 
                      p_chrdev);
    if (ret != 0)
    {
        PRTE("Failed to request IRQ %d\n", p_chrdev->irq_key.irq_num);
        // 请求中断失败，释放GPIO引脚
        gpio_free(p_chrdev->irq_key.gpio);
        return -ENODEV;
    }

    return 0;
}

/**
 * @brief  gpio_irq_uninit()
 * @note   gpio中断去初始化
 * @param [in]
 * @param [out]
 * @retval 
 */
static int gpio_irq_uninit(_CHAR_DEVICE *p_chrdev)
{
    // 释放中断
    free_irq(p_chrdev->irq_key.irq_num, p_chrdev);//这里的第二个参数一定要和request_irq()最后一个参数一致
    //释放GPIO引脚
    gpio_free(p_chrdev->irq_key.gpio);

    return 0;
}

static int scdev_ops_open(struct inode *pInode, struct file *pFile)
{
    //PRT("This is scdev_ops_open!dev name=%s\n", g_chrdev.dev_name);
    pFile->private_data = &g_chrdev;  // 设置私有数据
    return 0;
}

static ssize_t scdev_ops_read(struct file *pFile, char __user *buf, size_t size, loff_t *off)
{
    
    loff_t offset = *off; // 将读取数据的偏移量赋值给loff_t类型变量p
    size_t count = size;
    uint32_t key_value = 0;
    _CHAR_DEVICE *p_chrdev=(_CHAR_DEVICE *)pFile->private_data; //在write函数中读取private_data

    if (offset > BUFSIZE)
    {
        return 0;
    }

    if (count > BUFSIZE - offset)
    {
        count = BUFSIZE - offset;
    }

    // 获取按键值
    key_value = imx6ull_key_read(p_chrdev);
    PRT("key_value = %d\n", key_value);
    
    memcpy(p_chrdev->buf + offset, &key_value, sizeof(uint32_t));
    if (copy_to_user(buf, p_chrdev->buf + offset, count))
    {
        // 将mem中的值写入buf，并传递到用户空间
        PRT("copy_to_user error!\n");
        return -1;
    }
    #if 0
    int i = 0;
    for (i = 0; i < BUFSIZE; i++)
    {
        PRT("buf[%d] %c\n", i, p_chrdev->buf[i]); // 将mem中的值打印出来
    }
    
    PRT("read offset is %llu, count is %d\n", offset, count);
    #endif
    *off = *off + count; // 更新偏移值

    return count;
}

static ssize_t scdev_ops_write(struct file *pFile, const char __user *buf, size_t size, loff_t *off)
{
    loff_t offset = *off; // 将读取数据的偏移量赋值给loff_t类型变量p
    size_t count = size;
    _CHAR_DEVICE *p_chrdev=(_CHAR_DEVICE *)pFile->private_data; //在write函数中读取private_data

    if (offset > BUFSIZE)
    {
        return 0;
    }

    if (count > BUFSIZE - offset)
    {
        count = BUFSIZE - offset;
    }

    if (copy_from_user(p_chrdev->buf + offset, buf, count))
    { 
        // 将buf中的值，从用户空间传递到内核空间
        PRT("copy_to_user error \n");
        return -1;
    }
    //PRT("copy_from_user buf is %s\n", p_chrdev->buf + offset);
#if 0
    int i = 0;
    for (i = 0; i < BUFSIZE; i++)
    {
        PRT("buf[%d] %c\n", i, p_chrdev->buf[i]); // 将mem中的值打印出来
    }
    PRT("write offset is %llu, count is %d\n", offset, count); // 打印写入的值
    #endif
    *off = *off + count;                         // 更新偏移值

    return count;
}

static int scdev_ops_release(struct inode *pInode, struct file *pFile)
{
    //_CHAR_DEVICE *p_chrdev=(_CHAR_DEVICE *)pFile->private_data; //在write函数中读取private_data
    //PRT("This is scdev_ops_release!dev_name=%s\n", p_chrdev->dev_name);
    return 0;
}

static loff_t scdev_ops_llseek(struct file *pFile, loff_t offset, int whence)
{
    loff_t new_offset = 0; // 定义loff_t类型的新的偏移值
    switch (whence)    // 对lseek函数传递的whence参数进行判断
    {
        case SEEK_SET:
            if (offset < 0 || offset > BUFSIZE)
            {
                return -EINVAL; // EINVAL=22 表示无效参数
            }
            new_offset = offset; // 如果whence参数为SEEK_SET，则新偏移值为offset
            break;
        case SEEK_CUR:
            if ((pFile->f_pos + offset < 0) || (pFile->f_pos + offset > BUFSIZE))
            {
                return -EINVAL;
            }
            new_offset = pFile->f_pos + offset; // 如果whence参数为SEEK_CUR，则新偏移值为pFile->f_pos + offset，pFile->f_pos为当前的偏移值
            break;
        case SEEK_END:
            if (pFile->f_pos + offset < 0)
            {
                return -EINVAL;
            }
            new_offset = BUFSIZE + offset; // 如果whence参数为SEEK_END，则新偏移值为BUFSIZE + offset，BUFSIZE为最大偏移量
            break;
        default:
            break;
    }
    pFile->f_pos = new_offset; // 更新pFile->f_pos偏移值

    return new_offset;
}

static struct file_operations g_scdev_ops = {
    .owner = THIS_MODULE, // 将owner字段指向本模块，可以避免在模块的操作正在被使用时卸载该模块
    .open = scdev_ops_open,
    .read = scdev_ops_read,
    .write = scdev_ops_write,
    .release = scdev_ops_release,
    .llseek = scdev_ops_llseek,
}; // 定义file_operations结构体类型的变量g_cdev_dev_ops

static int scdev_create(_CHAR_DEVICE *p_chrdev)
{
    int ret;          // 定义int类型的变量ret，用来判断函数返回值
    int major, minor; // 定义int类型的主设备号major和次设备号minor

    ret = alloc_chrdev_region(&p_chrdev->dev_num, 0, 1, CHRDEV_NAME); // 自动获取设备号，设备名为chrdev_name
    if (ret < 0)
    {
        PRTE("alloc_chrdev_region is error!ret=%d\n", ret);
        goto err_alloc_devno;
    }
    major = MAJOR(p_chrdev->dev_num); // 使用MAJOR()函数获取主设备号
    minor = MINOR(p_chrdev->dev_num); // 使用MINOR()函数获取次设备号
    //PRT("major is %d, minor is %d !\n", major, minor);

    cdev_init(&p_chrdev->s_cdev, &g_scdev_ops); // 使用cdev_init()函数初始化p_chrdev->s_cdev结构体，并链接到 cdev_ops 结构体
    p_chrdev->s_cdev.owner = THIS_MODULE;          // 将owner字段指向本模块，可以避免在模块的操作正在被使用时卸载该模块
    ret = cdev_add(&p_chrdev->s_cdev, p_chrdev->dev_num, 1); // 使用cdev_add()函数进行字符设备的添加
    if (ret < 0)
    {
        PRTE("cdev_add is error !ret=%d\n", ret);
        goto err_cdev_add;
    }

    p_chrdev->class = class_create(THIS_MODULE, CLASS_NAME); // 使用class_create进行类的创建，类名称为 class_dev
    if(IS_ERR(p_chrdev->class))
    {
        ret = PTR_ERR(p_chrdev->class);
        goto err_class_create;
    }
    p_chrdev->device = device_create(p_chrdev->class, NULL, p_chrdev->dev_num, NULL, "%s", DEVICE_NAME); // 使用device_create进行设备的创建，设备名称为 device_dev
    if(IS_ERR(p_chrdev->device))
    {
        ret = PTR_ERR(p_chrdev->class);
        goto err_device_create;
    }
    snprintf (p_chrdev->dev_name, sizeof(p_chrdev->dev_name), "/dev/%s", DEVICE_NAME);

    //PRT("scdev_create %s success!\n", p_chrdev->dev_name);
    return 0;
    // 一些列的错误处理
err_device_create:
    class_destroy(p_chrdev->class);   // 删除创建的类
err_class_create:
    cdev_del(&p_chrdev->s_cdev);      // 使用cdev_del()函数进行字符设备的删除
err_cdev_add:
    unregister_chrdev_region(p_chrdev->dev_num, 1); //注销设备号
err_alloc_devno:
    return ret;
}

static void scdev_destroy(_CHAR_DEVICE *p_chrdev)
{
    // 需要注意的是， 字符设备的注册要放在申请字符设备号之后，
    // 字符设备的删除要放在释放字符驱动设备号之前。
    cdev_del(&p_chrdev->s_cdev);                    // 使用cdev_del()函数进行字符设备的删除
    unregister_chrdev_region(p_chrdev->dev_num, 1); // 释放字符驱动设备号
    device_destroy(p_chrdev->class, p_chrdev->dev_num); // 删除创建的设备
    class_destroy(p_chrdev->class);                 // 删除创建的类
    //PRT("scdev_destroy success!\n");
}

/**
 * @brief  sdrv_demo_init
 * @note   注册驱动
 * @param [in]
 * @param [out]
 * @retval 
 */
static __init int sdrv_demo_init(void)
{
    int ret = 0;
	printk("*** [%s:%d]Build Time: %s %s, git version:%s LINUX_VERSION=%d.%d.%d ***\n", __FUNCTION__,
           __LINE__, KERNEL_KO_DATE, KERNEL_KO_TIME, KERNEL_KO_VERSION, 
           (LINUX_VERSION_CODE >> 16) & 0xff, (LINUX_VERSION_CODE >> 8) & 0xff, LINUX_VERSION_CODE & 0xff);

	// 注册字符设备
    ret = scdev_create(&g_chrdev);
    if (ret < 0) 
    {
        PRTE("Failed to scdev_create!ret=%d\n", ret);
        goto err_scdev_create;
    }

    gpio_irq_init(&g_chrdev);
    //PRT("sdrv_demo module init success!\n");
	return 0;

err_scdev_create:
    return ret;
}

/**
 * @brief  sdrv_demo_exit
 * @note   注销驱动
 * @param [in]
 * @param [out]
 * @retval 
 */
static __exit void sdrv_demo_exit(void)
{
    gpio_irq_uninit(&g_chrdev);
    // 注销字符设备
    scdev_destroy(&g_chrdev);
    //PRT("sdrv_demo module exit!\n");
}

module_init(sdrv_demo_init); // 将__init定义的函数指定为驱动的入口函数
module_exit(sdrv_demo_exit); // 将__exit定义的函数指定为驱动的出口函数

/* 模块信息(通过 modinfo xxx.ko 查看) */
MODULE_LICENSE("GPL v2");            /* 源码的许可证协议 */
MODULE_AUTHOR("sumu");               /* 字符串常量内容为模块作者说明 */
MODULE_DESCRIPTION("Description");   /* 字符串常量内容为模块功能说明 */
MODULE_ALIAS("module's other name"); /* 字符串常量内容为模块别名 */
